<?php
/**
 * PsiFramework
 *
 * Php Simple Framework
 *
 * PHP version 5.3
 *
 * @category  PsiFramework.Plugins.Form.Classes.DAO
 * @package   PsiFramework.Plugins.Form.Classes.DAO
 * @author    Remi San <remi.san@gmail.com>
 * @copyright 2011 Remi San
 * @license   http://creativecommons.org/licenses/by/3.0/  Creative Commons CC BY 3.0
 * @version   SVN: <svn_id>
 * @link      http://www.psiframework.com/phpdoc/classes/db_\PsiFramework\Plugins\Form\Classes\DAO\FormDAO.html
 */

namespace PsiFramework\Plugins\Form\Classes\DAO;

import("Classes.DAO.AbstractDAO");
import("Classes.Util.Plugin");
import("Classes.Util.Manager.PluginManager");
import("Classes.Util.Parameters.ObjectParameters");
import("Plugins.Form.Classes.Model.Form");
import("Plugins.Form.Classes.Util.Json.JsonForm");
import("Plugins.Form.Classes.DAO.FormResponseDAO");

use \PsiFramework\Classes\DAO\AbstractDAO;
use \PsiFramework\Classes\Model\Object;
use \PsiFramework\Classes\Factory\DAOFactory;
use \PsiFramework\Classes\Util\Database\SqlQuery;
use \PsiFramework\Classes\Util\Database\Filters\QueryFilter;
use \PsiFramework\Classes\Util\Database\Query\SqlType;
use \PsiFramework\Classes\Util\Database\Query\SqlColumn;
use \PsiFramework\Classes\Util\Database\Query\SqlFunction;
use \PsiFramework\Classes\Util\Database\Query\SqlOperation;
use \PsiFramework\Classes\Util\Database\Query\SqlValue;
use \PsiFramework\Classes\Util\Parameters\Object\Operation;
use \PsiFramework\Classes\Util\Parameters\Object\Trigger;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\DeleteOperation;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\InsertOperation;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\SelectOperation;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\UpdateOperation;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\Elements\OperationMultipleClause;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\Elements\OperationObject;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\Elements\OperationColumn;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\Elements\OperationSimpleClause;
use \PsiFramework\Classes\Util\Parameters\Object\Operations\Elements\OperationValue;
use \PsiFramework\Classes\Util\Plugin;
use \PsiFramework\Classes\Util\Manager\PluginManager;
use \PsiFramework\Classes\Util\Parameters\DAO\ObjectParametersDAO;
use \PsiFramework\Classes\Util\Parameters\ObjectParameters;
use \PsiFramework\Classes\Util\Parameters\Object\AttributeParameters;
use \PsiFramework\Plugins\Form\Classes\Model\Form;
use \PsiFramework\Plugins\Form\Classes\Util\Json\JsonForm;
use \PsiFramework\Plugins\Form\Classes\Util\Json\JsonField;
use \PsiFramework\Plugins\Form\Classes\Util\Json\JsonFormElement;
use \PsiFramework\Plugins\Form\Classes\Util\Json\Elements\JsonFormInput;
use \PsiFramework\Plugins\Form\Classes\Util\Json\Elements\JsonFormText;
use \PsiFramework\Plugins\Form\Classes\Util\Json\Elements\JsonFormOption;
use \PsiFramework\Plugins\Form\Classes\Util\Json\Elements\JsonFormTitle;
use \PsiFramework\Plugins\Form\Classes\DAO\FormResponseDAO;

/**
 * The Form Data Access Object
 *
 * This is the default class for the Form Data Access Object
 *
 * @category PsiFramework.Plugins.Form.Classes.DAO
 * @package  PsiFramework.Plugins.Form.Classes.DAO
 * @author   Remi San <remi.san@gmail.com>
 * @license  http://creativecommons.org/licenses/by/3.0/  Creative Commons CC BY 3.0
 * @link     http://www.psiframework.com/phpdoc/classes/db_\PsiFramework\Plugins\Form\Classes\DAO\FormDAO.html
 */
class FormDAO extends AbstractDAO
{
    /**
     * @var FormDAO
     */
    protected static     $_singleton = null;
    
    /**
     * @var Plugin
     */
    protected /*Plugin*/ $_plugin = null;
    
    /**
     * FormDAO constructor
     * 
     * @param ObjectParameters $objectParameters The object parameters
     * 
     * @return FormDAO
     */
    protected function __construct(ObjectParameters $objectParameters=null)
    {
        parent::__construct($objectParameters);
        $this->_plugin = PluginManager::getInstance()->getPlugin("Form");
    }

    /**
     * The function to save a Form
     *
     * @param Form &$object            The object to save
     * @param bool $performTransaction Does the save action
     *                                 need a transaction ?
     * @param bool $checkSecurity      Does the save action
     *                                 need to check the security ?
     *
     * @see AbstractDAO::save()
     *
     * @return bool
     */
    public function save(
        Object   &$object,
        /*bool*/ $performTransaction=true,
        /*bool*/ $checkSecurity=true,
        /*bool*/ $propagation=true
    )
    {

        $ok = parent::save($object, $performTransaction, $checkSecurity);

        if ($ok && $propagation) {
            $fields = $object->getFields();
            
            $buildTable = false;
            
            if (count($fields)>0) {

                $this->buildForm($object);
                $ok = parent::save(
                    $object,
                    $performTransaction,
                    $checkSecurity
                );
                
                $buildTable = true;
            } else if (!is_null($object->getPartner())) {
                $buildTable = true;
            }

            if ($ok && $buildTable) {
                $formResponseDao
                    = FormResponseDAO::getInstance(
                        $object->getResponseParameters()
                    );
                
                if ($formResponseDao->tableExists() && $object->getResponseParameters()->isView()) {
                    $ok = $formResponseDao->deleteTable();
                }
                
                if ($ok) {
                    $ok = $formResponseDao->createTable();
                }
                
                if ($ok && !is_null($object->getResponseParameters())) {
                    $this->_plugin->addObjectParameter(
                        $object->getResponseParameters()
                    );
                }
            }
        }

        return $ok ;
    }

    /**
     * The function to delete a Form
     *
     * @param Form &$object            The object to delete
     * @param bool $performTransaction Does the delete action
     *                                 need a transaction ?
     * @param bool $checkSecurity      Does the delete action
     *                                 need to check the security ?
     *
     * @see AbstractDAO::delete()
     *
     * @return bool
     */
    public function delete(
        Object   &$object,
        /*bool*/ $performTransaction=true,
        /*bool*/ $checkSecurity=true
    )
    {

        $formResponseDao = FormResponseDAO::getInstance(
            $object->getResponseParameters()
        );
        $responses = $formResponseDao->retrieveAll();

        $this->_database->startTransaction();
        $ok = true ;

        foreach ($responses as $response) {
            $ok = $formResponseDao->delete($response, false, $checkSecurity);
            if (!$ok) {
                break;
            }
        }

        if ($ok) {
            $this->_database->commit();
        } else {
            $this->_database->rollback();
        }

        if ($ok) {
            $ok = $formResponseDao->deleteTable();
        }

        if ($ok) {
            $formFieldLinkDao = DAOFactory::getDAO('FormFieldLink');

            if ($performTransaction) {
                $this->_database->startTransaction();
            }

            $fields = $object->getFields();
            if (!is_null($fields)) {
                foreach ($fields as $formFieldLink) {
                    $ok = $formFieldLinkDao->delete(
                        $formFieldLink,
                        false,
                        $checkSecurity
                    );
                    if (!$ok) {
                        break;
                    }
                }
            }

            if ($ok) {
                $ok = parent::delete($object, false, $checkSecurity);
            }

            if ($performTransaction) {
                if ($ok) {
                    $this->_database->commit();
                } else {
                    $this->_database->rollback();
                }
            }
        }

        return $ok ;
    }

    /**
     * The function to retrieve the Form by partial name
     *
     * @param string $partialName The partial name to find
     * @param bool   $exactName   Does it have to be the exact name ?
     *
     * @return undefined
     */
    public function retrieveByPartialName(
        /*string*/ $partialName,
        /*bool*/   $exactName=false,
        /*bool*/   $minisite=false
    )
    {

        if ($exactName && isNullOrEmptyString($partialName)) {
            return null ;
        }

        $colNom = new SqlColumn("form_name");

        $value = mb_strtoupper($partialName, 'utf-8');
        if (!$exactName) {
            $value = '%' . $value . "%" ;
        }

        $sqlValue = new SqlValue($value);

        $upper = new SqlFunction("UPPER");
        $upper->addParameter($colNom);

        $op = null ;
        if ($exactName) {
            $op = new SqlOperation(SqlOperation::$equal);
        } else {
            $op = new SqlOperation(SqlOperation::$like);
        }

        if (!is_null($op)) {
            $op->addParameter($upper);
            $op->addParameter($sqlValue);

            $operation = null;
            if ($minisite) {

                $operation = new SqlOperation(SqlOperation::$and);
                
                $minisiteClause = new SqlOperation(SqlOperation::$equal);
                $minisiteClause->addParameter(new SqlColumn('minisite'));
                $minisiteClause->addParameter(new SqlValue(true));
                
                $operation->addParameter($op);
                $operation->addParameter($minisiteClause);
                
            } else {
                $operation = $op;
            }
            
            $result = $this->retrieveAll(new QueryFilter($operation));

            if (!$exactName) {
                return $result ;
            } else if (count($result)==1) {
                return $result[0] ;
            } else {
                return null ;
            }
        } else {
            return null ;
        }

    }

    /**
     * Creates the ObjectParameters base object for a form response
     *
     * @param string $name      The name of the object
     * @param string $tableName The name of the table
     * 
     * @return ObjectParameters The ObjectParameters base
     */
    public function getNewResponseParameters(
        /*string*/ $name,
        /*string*/ $tableName
    )
    {
        $responseParameters = new ObjectParameters(
            $name,
            "Plugins.Form.Classes.Model.FormResponse",
            "Plugins.Form.Classes.DAO.FormResponseDAO",
            $tableName,
            "form",
            array()
        );
        $responseParameters->addParameter(
            new AttributeParameters(
                array(
                    'name'=>'formResponseId',
                    'column'=>$tableName.'_id',
                    'type'=>'int',
                    'nullable'=>'true',
                    'primaryKey'=>'true'
                )
            )
        );
        $responseParameters->addParameter(
            new AttributeParameters(
                array(
                    'name'=>'user',
                    'column'=>'user_id',
                    'type'=>'User',
                    'nullable'=>'false'
                )
            )
        );
        $responseParameters->addParameter(
            new AttributeParameters(
                array(
                    'name'=>'modificationDate',
                    'column'=>'modification_date',
                    'type'=>'Date',
                    'sqlType'=>SqlType::$dateTime,
                    'nullable'=>'false'
                )
            )
        );
        $responseParameters->setView(true);
        return $responseParameters ;
    }

    /**
     * The function that returns the table name based on a name
     *
     * @param string $name The base name of the table
     *
     * @return string
     */
    public static function getTableName(/*string*/ $name)
    {
        return ObjectParameters::getTableName($name)."_form_response_view" ;
    }

    /**
     * The function to build the form
     * 
     * @param Form &$form The form to build
     * 
     * @return void
     */
    public function buildForm(Object &$form)
    {
        $name = $form->getName();

        $responseName = str_replace(' ', '', $name)."FormResponseView" ;
        $responseTableName = self::getTableName($name);

        $responseParameters = $this->getNewResponseParameters(
            $responseName,
            $responseTableName
        );

        $creationOperation = new SelectOperation();
        $creationWhereClause = new OperationMultipleClause();
        $firstField = true ;

        $formObject = new OperationObject($responseName);

        $globalJsonForm = new JsonForm();
        $form->getFields();
        $fields = $form->sortFields();

        if (!is_null($fields)) {

            $i = 0 ;
            $j = 0 ;

            $previousIdColumn = null ;
            $previousUserColumn = null ;

            for ($c = 0; $c<count($fields); $c++) {
                $fieldLink = $fields[$c] ;
                $field = $fieldLink->getField();
                $fieldType = $field->getType();
                $fieldName = $field->getName();
                $fieldGroup = new \stdClass();
                $fieldGroup->name = $fieldLink->getGroupName();
                $fieldGroup->title = $fieldLink->getGroupTitle();
                $rp = $fieldType->getResponseParameters();
                $structure = $field->getStructure();

                $currentObject = new OperationObject($rp->getName(), 't'.$j);

                $creationOperation->addObject($currentObject);

                $idColumn = new OperationColumn(
                    'formResponseId', $currentObject, $responseTableName.'_id'
                );
                $userColumn = new OperationColumn(
                    'user', $currentObject, 'user_id'
                );

                $formColumn = new OperationColumn('form', $currentObject);
                $fieldColumn = new OperationColumn('field', $currentObject);
                $creationWhereClause->addElement(
                    new OperationSimpleClause(
                        "=", $formColumn, new OperationValue($form->getId())
                    )
                );
                $creationWhereClause->addElement(
                    new OperationSimpleClause(
                        "=", $fieldColumn, new OperationValue($field->getId())
                    )
                );

                if ($firstField) {
                    $creationOperation->addColumn($idColumn);
                    $creationOperation->addColumn($userColumn);

                    $creationOperation->addColumn(
                        new OperationColumn(
                            'modificationDate',
                            $currentObject,
                            'modification_date'
                        )
                    );

                    $previousIdColumn = $idColumn ;
                    $previousUserColumn = $userColumn ;

                    $firstField = false ;
                } else {
                    $creationWhereClause->addElement(
                        new OperationSimpleClause(
                            "=", $previousIdColumn, $idColumn
                        )
                    );
                    $creationWhereClause->addElement(
                        new OperationSimpleClause(
                            "=", $previousUserColumn, $userColumn
                        )
                    );

                    $previousIdColumn = $idColumn ;
                    $previousUserColumn = $userColumn ;
                }

                $jsonForm = $structure ;
                $jsonForm->getElement(0)->setName($fieldName);

                $triggerBaseName = $responseTableName . '_' . $j ;

                $operationObject = new OperationObject($rp->getName());

                $idClause = new OperationSimpleClause(
                    "=",
                    new OperationColumn(
                        'formResponseId', $operationObject
                    ),
                    new OperationColumn(
                        'formResponseId', $operationObject, null, 'NEW'
                    )
                );
                $idClauseOld = new OperationSimpleClause(
                    "=",
                    new OperationColumn(
                        'formResponseId', $operationObject
                    ),
                    new OperationColumn(
                        'formResponseId', $operationObject, null, 'OLD'
                    )
                );
                $formClause = new OperationSimpleClause(
                    "=",
                    new OperationColumn('form', $operationObject),
                    new OperationValue($form->getId())
                );
                $fieldClause = new OperationSimpleClause(
                    "=",
                    new OperationColumn('field', $operationObject),
                    new OperationValue($field->getId())
                );

                $dateClause = new OperationSimpleClause(
                    "=",
                    new OperationColumn(
                        'modificationDate', $operationObject
                    ),
                    new OperationColumn(
                        'modificationDate', $operationObject, null, 'NEW'
                    )
                );

                $userClause = new OperationSimpleClause(
                    "=",
                    new OperationColumn('user', $operationObject),
                    new OperationColumn('user', $operationObject, null, 'NEW')
                );
                $userClauseOld = new OperationSimpleClause(
                    "=",
                    new OperationColumn('user', $operationObject),
                    new OperationColumn('user', $operationObject, null, 'OLD')
                );

                $operationWhereClause = new OperationMultipleClause("AND");
                $operationWhereClause->addElement($idClause);
                $operationWhereClause->addElement($userClause);
                $operationWhereClause->addElement($formClause);
                $operationWhereClause->addElement($fieldClause);

                $operationWhereClauseOld = new OperationMultipleClause("AND");
                $operationWhereClauseOld->addElement($idClauseOld);
                $operationWhereClauseOld->addElement($userClauseOld);
                $operationWhereClauseOld->addElement($formClause);
                $operationWhereClauseOld->addElement($fieldClause);

                $insertTrigger = new Trigger();
                $insertTrigger->setObject($formObject);
                $insertTrigger->setName('insert_'.$triggerBaseName);
                $insertTrigger->setMode("INSTEAD");
                $insertTrigger->setType(SqlQuery::$insert);
                $insertOperation = new InsertOperation();
                $insertOperation->addObject($operationObject);
                $insertOperation->addClause($idClause);
                $insertOperation->addClause($userClause);
                $insertOperation->addClause($formClause);
                $insertOperation->addClause($fieldClause);
                $insertOperation->addClause($dateClause);

                $updateTrigger = new Trigger();
                $updateTrigger->setObject($formObject);
                $updateTrigger->setName('update_'.$triggerBaseName);
                $updateTrigger->setMode("INSTEAD");
                $updateTrigger->setType(SqlQuery::$update);
                $updateOperation = new UpdateOperation();
                $updateOperation->addObject($operationObject);
                $updateOperation->addClause($dateClause);
                $updateOperation->setWhereClause($operationWhereClause);

                $deleteTrigger = new Trigger();
                $deleteTrigger->setObject($formObject);
                $deleteTrigger->setName('delete_'.$triggerBaseName);
                $deleteTrigger->setMode("INSTEAD");
                $deleteTrigger->setType(SqlQuery::$delete);
                $deleteOperation = new DeleteOperation();
                $deleteOperation->addObject($operationObject);
                $deleteOperation->setWhereClause($operationWhereClauseOld);

                if (!is_null($jsonForm) && !is_null($jsonForm->getElements())) {
                    $jsonField = $jsonForm->getElement(0);
                    $newJsonField = new JsonField();
                    $newJsonField->setName($fieldName);
                    $newJsonField->setGroup($fieldGroup);
                    $newJsonField->setClass($jsonField->getClass());
                    $newJsonField->setStyle($jsonField->getStyle());
                    $jsonElements = array();

                    foreach ($jsonField->getElements() as $element) {
                        if ($element instanceof JsonFormInput) {
                            $elementName = $element->getName();
                            $parameter = $rp->getParameter($elementName);

                            $parameterName = 'param'.$i ;
                            $parameterColumn = 'param'.$i ;

                            $creationOperation->addColumn(
                                new OperationColumn(
                                    $parameter->name,
                                    $currentObject,
                                    $parameterName
                                )
                            );

                            $newClause = new OperationSimpleClause(
                                "=",
                                new OperationColumn(
                                    $parameter->name, $operationObject
                                ),
                                new OperationColumn(
                                    $parameterName,
                                    $operationObject,
                                    null,
                                    'NEW'
                                )
                            );
                            $insertOperation->addClause($newClause);
                            $updateOperation->addClause($newClause);

                            $newParam = clone $parameter ;
                            $newParam->name = $parameterName ;
                            $newParam->column = $parameterColumn ;
                            $responseParameters->addParameter($newParam);
                            
                            $newElement = clone $element ;
                            $newElement->setName($parameterName);
                            $jsonElements[] = $newElement ;

                            $i++;
                        } else {
                            $jsonElements[] = $element ;
                        }
                    }
                    $newJsonField->setElements($jsonElements);
                    $globalJsonForm->addElement($newJsonField);
                }

                $insertTrigger->setOperation($insertOperation);
                $updateTrigger->setOperation($updateOperation);
                $deleteTrigger->setOperation($deleteOperation);

                $responseParameters->addTrigger($insertTrigger);
                $responseParameters->addTrigger($updateTrigger);
                $responseParameters->addTrigger($deleteTrigger);

                $j++ ;
            }

            $creationOperation->setWhereClause($creationWhereClause);
            $responseParameters->setConstructionOperation($creationOperation);
        }

        ObjectParametersDAO::$objectParameters[
            $responseParameters->getName()
        ] = $responseParameters ;

        $form->setResponseParameters($responseParameters);
        $form->setStructure($globalJsonForm);

    }

    public function prepareForCopy(Object &$object)
    {
        /* @var $object Form */
        
        //Duplicates the table and prevent to re-use the table from the original
        if (!is_null($object->getPartner())) {
            $uniqId = uniqid();
            $object->getResponseParameters()->setName('copy_of_'.$object->getResponseParameters()->getName().'_'.$uniqId);
            $object->getResponseParameters()->setTable('copy_of_'.$object->getResponseParameters()->getTable().'_'.$uniqId);
        }
    }
    
    function retrieveByResponseName(/*string*/ $name)
    {
        $colNom = new SqlColumn("form_response_name");
        $sqlValue = new SqlValue($name);
        $op = new SqlOperation(SqlOperation::$equal);
        
        $op->addParameter($colNom);
        $op->addParameter($sqlValue);
        
        $result = $this->retrieveAll(new QueryFilter($op));
        
        if (count($result)==1) {
            return $result[0] ;
        } else {
            return null ;
        }
    }
    
    /**
     * The function to call instead of the constructor.
     *
     * @param ObjectParameters $objectParameters The object parameters
     *                                           for Form
     *
     * @return FormDAO
     */
    public static function getInstance(ObjectParameters $objectParameters)
    {

        if (self::$_singleton==null) {
            self::$_singleton = new FormDAO($objectParameters);
        }

        return self::$_singleton;
    }
}
